﻿#region namespaces

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Reflection;
using System.Runtime.InteropServices;

using Tao.OpenGl;
using Tao.FreeGlut;
using Tao.Platform.Windows;
using Tao.FreeType;
using System.Windows.Forms.Integration;

using RS.Helper.WPF.OGL;
using RS.Host;

using RSCore.Helper.WPF;

using RSEngine;
using RSEngine.Math;
using RSEngine.Manipulators;
using RSEngine.Core;
using RSEngine.Renderer;
using RSEngine.Renderer.Hardware;

#endregion

#region Copyright (c) 2006, 2007, 2008, 2009 Render Studio Foundation. All rights reserved.

/*
 Copyright (c) 2006, 2007, 2008, 2009 Render Studio Foundation. All rights reserved.
 This work is copyrighted in its entirety. Permission to use "Render Studio"
 material may be granted only after we receive a written request from you,
 and you receive written permission from us.

 While Render Studio Foundation provides the information herein to anyone,
 we retain copyright on all code, text, graphic images, and other content.
 This means that you may not distribute the code, the text or graphics to others without the
 express written permission of Render Studio Foundation; "mirror" this information on your server
 without our permission; or modify or reuse the code, text or graphics on our servers.
 You may print copies of the information for your own personal use; and, you may reference
 the Service from your own documents. Any commercial use of this material is prohibited without
 the written permission of Render Studio Foundation. In all copies of this information, you must
 retain this notice and any other copyright notices originally included with such information.

 This material may be protected by U.S. and International Copyright laws, and you are legally
 bound by the copyright notices on those systems.

 "Render Studio" logos and other official symbols or graphics may not be used in connection with any
 product or service that has not been expressly authorized in advance in writing or in any manner
 that is likely to cause confusion among the users of this or other Web sites or among the public.
 All other seals, graphics or trademarks of organizations not affiliated with Render Studio Foundation
 that appear on this site are the property of their respective owners.

 Render Studio Foundation reserve all other rights. 
*/

#endregion

#region Render Studio Source Code Licence

/*
This license governs use of the Render Studio software. If you use the software, you accept this license. If you do not accept the license, do not use the software.

1. Summary

The source code is made available to view for reference purposes or to contribute to the project only. Developers may not distribute or modify the code for commercial or non-commercial purposes without express Agreement of Render Studio Foundation.

2. Definitions

The terms "reproduce," "reproduction," and "distribution" have the same meaning here as under U.S. copyright law.
"Licensor" means the foundation "Render Studio Foundation".
"You" means the licensee of the software, who is not engaged in designing, developing, or testing other software, that has the same or substantially the same features or functionality as the software.
"Your company" means the company you worked for when you downloaded the software.
"Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes of debugging and maintaining your products. For clarity, "reference use" does NOT include (a) the right to use the software for purposes of designing, developing, or testing other software, that has the same or substantially the same features or functionality as the software, and (b) the right to distribute the software outside of your company.
"Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor under this license.

3. Grant of Rights

(A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, worldwide, royalty-free copyright license to reproduce the software for reference use.
(B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, worldwide, royalty-free patent license under licensed patents for reference use.
4. Limitations
(A) No Trademark License- This license does not grant you any rights to use the Licensor's name, logo, or trademarks.
(B) If you begin patent litigation against the Licensor over patents that you think may apply to the software (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically.
(C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
 */

#endregion

namespace RS.UI
{

    public partial class ViewPort3D : UserControl, IRendererTarget, IArrangeSupport
    {

        #region Variables

        /// <summary>
        /// The space size of the viewport (i.e. 100 cm large etc...)
        /// </summary>
        private const float VIEWPORT_SPACE_SIZE = 100;

        //---- Controls
        private ControlType _layoutType;

        //---- viewport Id
        static private int ViewPortCount = 0;
        private int _viewPortId = ViewPortCount++;

        //---- Camera settings
        public Camera Camera = new Camera(null);

        //---- Moving action
        private Point _lastPosition;

        //---- Arc ball
        private ArcBall _arcBall;

        //---- Ortho
        private float _orthoScale = 1f;

        //---- Guide Cube
        static private int GUIDECUBE_VIEWPORTSIZE = 100;
        static private float _cubeSize = 50f;

        static private float[] _guideCubeLightAmbient = new float[] { 0.3f, 0.3f, 0.3f, 1.0f };
        static private float[] _guideCubeLightDiffuseSpecular = new float[] { 0.75f, 0f, 0f, 1f };

        static private float[] _guideCubeColor = new float[] { 0.6f, 0f, 0f };
        static private float[] _guideCubeHighlightColor = new float[] { 0.4f, 0.9f, 0.9f };

        private int _frontTextureId = -1;
        private int _backTextureId = -1;
        private int _leftTextureId = -1;
        private int _rightTextureId = -1;
        private int _topTextureId = -1;
        private int _bottomTextureId = -1;

        private System.Drawing.Bitmap _selectionBitmap = new System.Drawing.Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

        private GuideCubeNames _guideCubeSelection = GuideCubeNames.None;

        //----
        private GLRenderer _renderer = new GLRenderer();
        private GLRenderParameters _parameters = new GLRenderParameters();

        #endregion

        #region Constructor

        static ViewPort3D()
        {
            Glut.glutInit();
        }

        public ViewPort3D(ControlType viewType)
        {
            InitializeComponent();

            _layoutType = viewType;

            //---- Radio buttons
            rbWireframe.GroupName = "RenderingStyle" + _viewPortId;
            rbWireframeOnShaded.GroupName = "RenderingStyle" + _viewPortId;
            rbShaded.GroupName = "RenderingStyle" + _viewPortId;

            //---- OpenGL Control
            oglImage.OnOGLRender += new EventHandler(oglImage_OnOGLRender);
            oglImage.SizeChanged += new SizeChangedEventHandler(oglImage_SizeChanged);
            oglImage.MouseMove += new MouseEventHandler(oglImage_MouseMove);
            oglImage.MouseDown += new MouseButtonEventHandler(oglImage_MouseDown);
            oglImage.MouseUp += new MouseButtonEventHandler(oglImage_MouseUp);
            oglImage.MouseLeave += new MouseEventHandler(oglImage_MouseLeave);
            oglImage.MouseEnter += new MouseEventHandler(oglImage_MouseEnter);
            oglImage.MouseWheel += new MouseWheelEventHandler(oglImage_MouseWheel);

            oglImage.MouseEnter += new MouseEventHandler(ViewPort3D_MouseEnter);
            oglImage.MouseLeave += new MouseEventHandler(ViewPort3D_MouseLeave);
            this.MouseEnter += new MouseEventHandler(ViewPort3D_MouseEnter);
            this.MouseLeave += new MouseEventHandler(ViewPort3D_MouseLeave);

            this.Loaded += new RoutedEventHandler(ViewPort3D_Loaded);

            //---- Keyboard
            Keyboard.AddKeyUpHandler(this, new KeyEventHandler(OnKeyUp));

            //---- Engine
            Engine.BeforeSceneUpdated += new SceneUpdateEventHandler(Engine_BeforeSceneUpdated);

            //---- View settings
            ResetView();
        }

        #endregion

        #region Properties

        public ControlType ControlType
        {
            get
            {
                return _layoutType;
            }
        }

        #endregion

        #region OnArrange

        public void OnArrange()
        {
            _frontTextureId = -1;
            _backTextureId = -1;
            _leftTextureId = -1;
            _rightTextureId = -1;
            _topTextureId = -1;
            _bottomTextureId = -1;

            // WARNING :
            // Here we reset the display-list, otherwise 
            // we have no display after an 'arrang'.
            // We don't know why... this method fix the problem
            _renderer.Reset();
        }

        #endregion

        #region ResetView

        private void ResetView()
        {
            Camera = LookAt(VIEWPORT_SPACE_SIZE);
        }

        #endregion

        #region Refresh

        public void Refresh()
        {
            oglImage.Refresh();
        }

        #endregion

        #region Engine_BeforeSceneUpdated

        void Engine_BeforeSceneUpdated(RSEngine.Core.RSBase source, SceneUpdateAction action)
        {
            Dispatcher.BeginInvoke(
                DispatcherPriority.Send,
                new Action(delegate()
                {
                    AsyncResetScene(action);
                })
                );
        }

        private void AsyncResetScene(SceneUpdateAction action)
        {
            if (action == SceneUpdateAction.Reset)
            {
                oglImage.MakeCurrent();
                _renderer.Reset();
            }

            oglImage.Refresh();
        }

        #endregion

        #region ViewPort3D_Loaded

        void ViewPort3D_Loaded(object sender, RoutedEventArgs e)
        {
            if (_arcBall == null)
                _arcBall = new ArcBall((float)oglImage.ActualWidth, (float)oglImage.ActualHeight);

            Tao.OpenGl.Gl.ReloadFunctions();

            oglImage.InitializeContexts();
            oglImage_SizeChanged(null, null);
            oglImage.Refresh();
        }

        #endregion

        #region OnUpdateViewport

        private float OnUpdateViewport()
        {
            int width = (int)oglImage.ActualWidth;
            int height = (int)oglImage.ActualHeight;
            if (height == 0)
                height = 1;

            //---- Reset The Current Viewport
            Gl.glViewport(0, 0, width, height);

            // Returns the aspect ratio
            return ((float)width) / height;
        }

        #endregion

        #region oglImage_SizeChanged

        void oglImage_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (_arcBall == null)
                return;

            if (oglImage.ActualWidth == 0 && oglImage.ActualHeight == 0)
                return;

            _arcBall.SetBounds((float)oglImage.ActualWidth, (float)oglImage.ActualHeight);

            lock (OGLImage.RENDER_LOCK)
            {
                try
                {
                    oglImage.MakeCurrent();
                }
                catch (Exception ex) { }

                //---- Reset The Current Viewport
                Camera.AspectRatio = OnUpdateViewport();

                //---- Set up the view
                //Gl.glMatrixMode(Gl.GL_PROJECTION);
                //Gl.glLoadIdentity();

                //if (View == LayoutType.Perspective)
                //{
                //    // Calculate The Aspect Ratio Of The Window
                //    Glu.gluPerspective(45, _aspectRatio, 0, 100);
                //}
                //else
                //{
                //    //Set the orthogonal view
                //    Gl.glOrtho(-_aspectRatio / 2, _aspectRatio / 2, _viewPortYFactor / 2, -_viewPortYFactor / 2, 0, 100);
                //}

                //---- Apply the transformations to the model now
                //Gl.glMatrixMode(Gl.GL_MODELVIEW);
                //Gl.glLoadIdentity();
            }
        }

        #endregion

        #region oglImage_OnOGLRender

        void oglImage_OnOGLRender(object sender, EventArgs e)
        {
            Render();
        }

        #endregion

        #region Event : OnKeyUp

        void OnKeyUp(object sender, KeyEventArgs args)
        {
            Point mouse = MouseHelper.GetPosition(this);

            if (Keyboard.Modifiers != RSUIKeys.CameraMove)
            {
                if (_isMoving)
                    StopMove((int)mouse.X, (int)mouse.Y);
                if (_isZooming)
                    StopZoom((int)mouse.X, (int)mouse.Y);
                if (_isArcBall)
                    StopArcBall((int)mouse.X, (int)mouse.Y);
            }

            if (args.Key == RSUIKeys.FullScreen)
                Arranger.Arrange(ArrangeSchema.Full);
        }

        #endregion

        #region Event : Mouse

        void ViewPort3D_MouseEnter(object sender, EventArgs e)
        {
            OnMouseInOut();
        }

        void ViewPort3D_MouseLeave(object sender, EventArgs e)
        {
            OnMouseInOut();
        }

        void oglImage_MouseLeave(object sender, EventArgs e)
        {
            Point mouse = MouseHelper.GetPosition(oglImage);
            int x = (int)mouse.X;
            int y = (int)mouse.Y;

            if (_isMoving)
                StopMove((int)mouse.X, (int)mouse.Y);
            if (_isZooming)
                StopZoom((int)mouse.X, (int)mouse.Y);
            if (_isArcBall)
                StopArcBall((int)mouse.X, (int)mouse.Y);

            RSHost.UIService.OnMouseOutRenderZone();
        }

        void oglImage_MouseEnter(object sender, MouseEventArgs e)
        {
            // Set up the information related to the render zone
            RSHost.UIService.RenderZoneCamera = Camera;
            RSHost.UIService.RenderZoneWidth = (int)oglImage._ogl.Width;
            RSHost.UIService.RenderZoneHeight = (int)oglImage._ogl.Height;

            RSHost.UIService.OnMouseEnterRenderZone();
        }

        private void OnMouseInOut()
        {
            Point mouse = MouseHelper.GetPosition(this);

            if (mouse.X > 0 && mouse.X < ActualWidth &&
                mouse.Y > 0 && mouse.Y < ActualHeight)
                OnMouseEnter();
            else
                OnMouseLeave();
        }

        private void OnMouseEnter()
        {
            Focus();
            bdrFocus.BorderBrush = (Brush)FindResource("RSColor1ABrush");
            Arranger.OnMouseEnterElement(this);
        }

        private void OnMouseLeave()
        {
            bdrFocus.BorderBrush = new SolidColorBrush(Color.FromArgb(0, 1, 1, 1));
            Arranger.OnMouseOutElement(this);
        }

        void oglImage_MouseDown(object sender, MouseButtonEventArgs e)
        {
            //---- Prepare
            Focus();

            Point mouse = MouseHelper.GetPosition(oglImage);

            int x = (int)mouse.X;
            int y = (int)mouse.Y;

            //---- Scene manipulation
            if (Keyboard.Modifiers != RSUIKeys.CameraMove)
            {
                //---- Host notification
                if (MouseHelper.LeftButton == MouseButtonState.Pressed)
                    RSHost.UIService.OnMouseDownRenderZone(x, y, MouseButton.Left, MouseButtonState.Pressed);
                else if (MouseHelper.MiddleButton == MouseButtonState.Pressed)
                    RSHost.UIService.OnMouseDownRenderZone(x, y, MouseButton.Middle, MouseButtonState.Pressed);
                else if (MouseHelper.RightButton == MouseButtonState.Pressed)
                    RSHost.UIService.OnMouseDownRenderZone(x, y, MouseButton.Right, MouseButtonState.Pressed);

                return;
            }

            if (MouseHelper.LeftButton == MouseButtonState.Pressed)
            {
                if (!_isArcBall)
                    StartArcBall(x, y);
            }

            else if (MouseHelper.MiddleButton == MouseButtonState.Pressed)
            {
                if (!_isMoving)
                    StartMove(x, y);
            }

            else if (MouseHelper.RightButton == MouseButtonState.Pressed)
            {
                if (!_isZooming)
                    StartZoom(x, y);
            }
        }

        void oglImage_MouseUp(object sender, MouseButtonEventArgs e)
        {
            Focus();
            Point mouse = MouseHelper.GetPosition(oglImage);
            if (Keyboard.Modifiers != RSUIKeys.CameraMove)
            {
                AutoSwitchLayout();
                return;
            }

            int x = (int)mouse.X;
            int y = (int)mouse.Y;

            if (_isArcBall && MouseHelper.LeftButton == MouseButtonState.Released)
                StopArcBall(x, y);

            if (_isMoving && MouseHelper.MiddleButton == MouseButtonState.Released)
                StopMove(x, y);

            if (_isZooming && MouseHelper.RightButton == MouseButtonState.Released)
                StopZoom(x, y);
        }

        void oglImage_MouseMove(object sender, MouseEventArgs e)
        {
            Focus();

            Point mouse = MouseHelper.GetPosition(oglImage);
            int x = (int)mouse.X;
            int y = (int)mouse.Y;

            PickingGuideCube(mouse);

            if (Keyboard.Modifiers != RSUIKeys.CameraMove)
                return;

            if (_isArcBall)
                OnArcBall(x, y);

            else if (_isMoving)
                OnMove(x, y);

            else if (_isZooming)
                OnZoom(x, y);
        }

        void oglImage_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (e.Delta > 0)
                ZoomCamera(0.1f);
            else
                ZoomCamera(-0.1f);
        }

        #endregion

        #region StartMove / StopMove / Move

        private bool _isMoving = false;

        private void StartMove(int x, int y)
        {
            _lastPosition = new Point(x, y);
            _isMoving = true;
        }

        private void OnMove(int x, int y)
        {
            double sceneX = x - _lastPosition.X;
            double sceneY = y - _lastPosition.Y;

            //if (LayoutType != LayoutType.ViewPort_Perspective)
            //    ConvertMouseToScene(ref sceneX, ref sceneY);

            //MoveCameraX(sceneX);
            //MoveCameraY(sceneY);

            //MoveCamera(sceneX, sceneY);

            MoveCamera(_lastPosition, new Point(x, y));

            _lastPosition = new Point(x, y);

            oglImage.Refresh();
        }

        private void StopMove(int x, int y)
        {
            double sceneX = x - _lastPosition.X;
            double sceneY = y - _lastPosition.Y;

            //if (LayoutType != LayoutType.ViewPort_Perspective)
            //    ConvertMouseToScene(ref sceneX, ref sceneY);

            //MoveCameraX(sceneX);
            //MoveCameraY(sceneY);
            //MoveCamera(sceneX, sceneY);
            MoveCamera(_lastPosition, new Point(x, y));

            oglImage.Refresh();
            _isMoving = false;
        }

        #endregion

        #region MoveCamera

        private void MoveCamera(Point lastMouse, Point newMouse)
        {
            OnUpdateViewport();
            ApplySceneMode();

            lastMouse.Y = oglImage.ActualHeight - 1 - lastMouse.Y;
            newMouse.Y = oglImage.ActualHeight - 1 - newMouse.Y;

            #region Get the projection/screen information

            Matrix4 projectionMatrix = Matrix4.Identity.Clone();
            Gl.glGetFloatv(Gl.GL_PROJECTION_MATRIX, projectionMatrix.Values);

            Matrix4 modelMatrix = Matrix4.Identity.Clone();
            Gl.glGetFloatv(Gl.GL_MODELVIEW_MATRIX, modelMatrix.Values);

            int[] viewport = new int[4];
            Gl.glGetIntegerv(Gl.GL_VIEWPORT, viewport);

            #endregion

            #region Unproject

            // Use the CenterZ(0,0,0) as Z relative point for moving
            Vector3 center = new Vector3();
            gluProject(0, 0, 0, modelMatrix.Values, projectionMatrix.Values, viewport, out center.x, out center.y, out center.z);

            Vector3 last3 = new Vector3();
            Vector3 new3 = new Vector3();
            gluUnProject((float)lastMouse.X, (float)lastMouse.Y, center.z, modelMatrix.Values, projectionMatrix.Values, viewport, out last3.x, out last3.y, out last3.z);
            gluUnProject((float)newMouse.X, (float)newMouse.Y, center.z, modelMatrix.Values, projectionMatrix.Values, viewport, out new3.x, out new3.y, out new3.z);

            #endregion

            Vector3 delta3 = new3 - last3;

            // Move the camera
            Camera.Eye -= delta3;
            Camera.EyeAt -= delta3;
        }

        #endregion

        #region StartZoom / StopZoom / Zoom

        private bool _isZooming = false;

        private void StartZoom(int x, int y)
        {
            _lastPosition = new Point(x, y);
            _isZooming = true;
        }

        private void OnZoom(int x, int y)
        {
            double sceneX = x - _lastPosition.X;
            double sceneY = y - _lastPosition.Y;
            ConvertMouseToScene(ref sceneX, ref sceneY);

            //float orientation = Math.Tan(sceneX + sceneY);
            float dist = (float)Math.Sqrt(sceneX * sceneX + sceneY * sceneY);

            if (y > _lastPosition.Y)
                ZoomCamera(dist);
            else
                ZoomCamera(-dist);

            _lastPosition = new Point(x, y);

            oglImage.Refresh();
        }

        private void StopZoom(int x, int y)
        {
            double sceneX = x - _lastPosition.X;
            double sceneY = y - _lastPosition.Y;

            ConvertMouseToScene(ref sceneX, ref sceneY);

            float dist = (float)Math.Sqrt(sceneX * sceneX + sceneY * sceneY);

            if (y > _lastPosition.Y)
                ZoomCamera(dist);
            else
                ZoomCamera(-dist);

            oglImage.Refresh();
            _isZooming = false;
        }

        #endregion

        #region ZoomCamera...

        private void ZoomCamera(float zoomFactor)
        {
            _orthoScale += zoomFactor;

            if (_orthoScale == 0)
                _orthoScale = float.Epsilon;

            float distX = (Camera.EyeAt.x - Camera.Eye.x);
            float distY = (Camera.EyeAt.y - Camera.Eye.y);
            float distZ = (Camera.EyeAt.z - Camera.Eye.z);

            Camera.Eye.x = Camera.Eye.x + distX * zoomFactor;
            Camera.Eye.y = Camera.Eye.y + distY * zoomFactor;
            Camera.Eye.z = Camera.Eye.z + distZ * zoomFactor;

            oglImage.Refresh();
        }

        #endregion

        #region StartArcBall / StopArcBall / ArcBall

        private bool _isArcBall = false;

        private void StartArcBall(int x, int y)
        {
            if (ControlType != ControlType.ViewPort_Perspective)
                return;

            Point position = new Point(x, y);
            _isArcBall = true;

            _arcBall.StartDrag(position);
        }

        private void OnArcBall(int x, int y)
        {
            if (ControlType != ControlType.ViewPort_Perspective)
                return;

            Point position = new Point(x, y);

            _arcBall.Drag(position);
            UpdateLookAt();

            oglImage.Refresh();
        }

        private void StopArcBall(int x, int y)
        {
            if (ControlType != ControlType.ViewPort_Perspective)
                return;

            Point position = new Point(x, y);
            _arcBall.Drag(position);
            UpdateLookAt();

            oglImage.Refresh();
            _isArcBall = false;
        }

        #endregion

        #region LookAt ...

        /// <summary>
        /// Update the LookAt coordinate for the current ArcBall rotation
        /// </summary>
        private void UpdateLookAt()
        {
            float distance = (Camera.Eye - Camera.EyeAt).Length;
            Camera layoutLookAt = LookAt(distance);

            Vector3 translation = Camera.EyeAt.Clone();

            Camera.Eye = translation + _arcBall.GlobalRotation * layoutLookAt.Eye;
            Camera.EyeAt = translation + _arcBall.GlobalRotation * layoutLookAt.EyeAt;
            Camera.EyeUp = _arcBall.GlobalRotation * layoutLookAt.EyeUp;
        }

        /// <summary>
        /// Returns the look at vector for this layout
        /// </summary>
        private Camera LookAt(float distance)
        {
            return LookAt(this.ControlType, distance);
        }

        /// <summary>
        /// Returns the look at vector for a layout
        /// </summary>
        private Camera LookAt(ControlType layout, float distance)
        {
            Camera info = new Camera(null);
            info.EyeAt.x = 0;
            info.EyeAt.y = 0;
            info.EyeAt.z = 0;

            switch (layout)
            {
                case ControlType.ViewPort_Front:
                    info.Eye.x = 0;
                    info.Eye.y = 0;
                    info.Eye.z = distance;
                    info.EyeUp.x = 0;
                    info.EyeUp.y = 1;
                    info.EyeUp.z = 0;
                    break;
                case ControlType.ViewPort_Back:
                    info.Eye.x = 0;
                    info.Eye.y = 0;
                    info.Eye.z = -distance;
                    info.EyeUp.x = 0;
                    info.EyeUp.y = 1;
                    info.EyeUp.z = 0;
                    break;
                case ControlType.ViewPort_Top:
                    info.Eye.x = 0;
                    info.Eye.y = distance;
                    info.Eye.z = 0;
                    info.EyeUp.x = 0;
                    info.EyeUp.y = 0;
                    info.EyeUp.z = -1;
                    break;
                case ControlType.ViewPort_Bottom:
                    info.Eye.x = 0;
                    info.Eye.y = -distance;
                    info.Eye.z = 0;
                    info.EyeUp.x = 0;
                    info.EyeUp.y = 0;
                    info.EyeUp.z = 1;
                    break;
                case ControlType.ViewPort_Right:
                    info.Eye.x = distance;
                    info.Eye.y = 0;
                    info.Eye.z = 0;
                    info.EyeUp.x = 0;
                    info.EyeUp.y = 1;
                    info.EyeUp.z = 0;
                    break;
                case ControlType.ViewPort_Left:
                    info.Eye.x = -distance;
                    info.Eye.y = 0;
                    info.Eye.z = 0;
                    info.EyeUp.x = 0;
                    info.EyeUp.y = 1;
                    info.EyeUp.z = 0;
                    break;
                case ControlType.ViewPort_Perspective:
                    //eye.x = Math.Cos(Math.PI / 4) * eyeDepth * 1.5;
                    //eye.z = -Math.Sin(Math.PI / 4) * eyeDepth * 1.5;
                    //eye.y = eyeDepth / 3;
                    //eyeNormal.x = eye.x;
                    //eyeNormal.y = eye.y + 1;
                    //eyeNormal.z = eye.z;

                    info.Eye.x = 0;
                    info.Eye.z = distance;
                    info.Eye.y = 0;
                    info.EyeUp.x = 0;
                    info.EyeUp.y = 1;
                    info.EyeUp.z = 0;
                    //_arcBall.Rotate(Math.PI / 4, Math.PI / 4, 0);
                    //_arcBall.Translate(0, 30, 0);
                    break;
            }

            return info;
        }

        #endregion

        #region Render

        private void Render()
        {
            //---- Set up the context
            Gl.glShadeModel(Gl.GL_SMOOTH);  // Set the shading model to smooth 
            Gl.glHint(Gl.GL_PERSPECTIVE_CORRECTION_HINT, Gl.GL_NICEST);	// nice perspective calculations

            Gl.glClearColor(
                RSColors.ViewPort_Background_R,
                RSColors.ViewPort_Background_G,
                RSColors.ViewPort_Background_B, 1);

            Gl.glClearDepth(1.0);         // Set the Depth buffer value (ranges[0,1])
            Gl.glEnable(Gl.GL_DEPTH_TEST);  // Enable Depth test
            Gl.glDepthRange(0, 1); // maps window z coordinate to the range 0,1
            Gl.glDepthFunc(Gl.GL_LEQUAL);   // If two objects on the same coordinate show the first drawn
            Gl.glDepthMask(Gl.GL_TRUE);

            //Gl.glEnable(Gl.GL_LINE_SMOOTH);
            Gl.glHint(Gl.GL_LINE_SMOOTH_HINT, Gl.GL_DONT_CARE);

            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

            //---- Update the viewport
            OnUpdateViewport();

            //---- Render
            ApplySceneMode();

            Render_Grid();

            _parameters.Camera.Eye = Camera.Eye;
            _parameters.Camera.EyeAt = Camera.EyeAt;
            _parameters.Camera.EyeUp = Camera.EyeUp;
            _parameters.Selection = RSHost.UIService.RenderZoneSelection;
            _parameters.SelectionMode = RSHost.UIService.RenderZoneSelectionMode;
            _renderer.Render(this, Engine.Scene, _parameters);

            Unapply_SceneMode();

            //---- Cube
            Render_GuideCube();

            //---- Axis
            Render_Axis();
        }

        #endregion

        #region Render : Projection modes

        private void ApplySceneMode()
        {
            //---- Set up the view
            Gl.glMatrixMode(Gl.GL_PROJECTION); // Set up the projection
            Gl.glLoadIdentity();

            if (ControlType == ControlType.ViewPort_Perspective)
            {
                // Calculate The Aspect Ratio Of The Window
                gluPerspective(45, Camera.AspectRatio, 1, 10000); // 0.1 cause a strange effect !

                // gluPerspective(fovy, aspect, near, far);
                // glFrustum(-aspect*near*tan(M_PI*fovy/360),aspect*near*tan(M_PI*fovy/360),-near*tan(M_PI*fovy/360),near*tan(M_PI*fovy/360),near,far);

                //lower = (zPlane == 0) ? zPlane - 10 : (zPlane - 2 * (zPlane < 0 ? -zPlane : zPlane));
                //upper = (zPlane == 0) ? zPlane + 10 : (zPlane + 2 * (zPlane < 0 ? -zPlane : zPlane));
                //Glu.gluLookAt(0, 0, upper, 0, 0, lower, 0, 1, 0);

                //(100 - 2 * 100);
                //(100 + 2 * 100);
                //near = upper - zPlane - 0.00001;
                //far = upper - zPlane + 0.00001;

                //Glu.gluPerspective(45, _aspectRatio, 100 - 0.00001, 100 + 0.00001);

                //Gl.glFrustum(-_aspectRatio * VIEWPORT_SPACE_SIZE / 2, _aspectRatio * VIEWPORT_SPACE_SIZE / 2, -VIEWPORT_SPACE_SIZE / 2, VIEWPORT_SPACE_SIZE / 2, 1, 10000);
                //Gl.glFrustum(-1.0, 1.0, -1.0, 1.0, 1.5, 20.0);
            }
            else
            {
                // Set the orthogonal view
                Gl.glOrtho(-Camera.AspectRatio * VIEWPORT_SPACE_SIZE / 2, Camera.AspectRatio * VIEWPORT_SPACE_SIZE / 2, VIEWPORT_SPACE_SIZE / 2, -VIEWPORT_SPACE_SIZE / 2, 0, 10000);

                // Set the zoom factor
                Gl.glScaled(_orthoScale, _orthoScale, _orthoScale);
            }

            Glu.gluLookAt(
                Camera.Eye.x, Camera.Eye.y, Camera.Eye.z,
                Camera.EyeAt.x, Camera.EyeAt.y, Camera.EyeAt.z,
                Camera.EyeUp.x, Camera.EyeUp.y, Camera.EyeUp.z);

            //---- Set up the model
            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();
        }

        private void Unapply_SceneMode()
        {
            //---- Reset to default materials
            Gl.glMaterialfv(Gl.GL_FRONT_AND_BACK, Gl.GL_AMBIENT, new float[] { 0.2f, 0.2f, 0.2f, 1f });
            Gl.glMaterialfv(Gl.GL_FRONT_AND_BACK, Gl.GL_DIFFUSE, new float[] { 0.8f, 0.8f, 0.8f, 1f });
            Gl.glMaterialfv(Gl.GL_FRONT_AND_BACK, Gl.GL_SPECULAR, new float[] { 0f, 0f, 0f, 1f });
            Gl.glMaterialfv(Gl.GL_FRONT_AND_BACK, Gl.GL_EMISSION, new float[] { 0f, 0f, 0f, 1f });
            Gl.glMaterialf(Gl.GL_FRONT_AND_BACK, Gl.GL_SHININESS, 0);
        }

        #endregion

        #region Render_Grid

        /// <summary>
        /// Render the grid on the X , Z plan.
        /// </summary>
        private void Render_Grid()
        {
            //---- Draw the grid
            int gridSize = 24;
            float gridStep = VIEWPORT_SPACE_SIZE / gridSize;

            Gl.glColor3fv(RSColors.ViewPort_Grid);
            Gl.glLineWidth(1f);

            float x = -VIEWPORT_SPACE_SIZE / 2;
            for (int index = 0; index <= gridSize; index++)
            {
                Gl.glBegin(Gl.GL_LINES);
                Gl.glVertex3f(x, 0, -VIEWPORT_SPACE_SIZE / 2);
                Gl.glVertex3f(x, 0, VIEWPORT_SPACE_SIZE / 2);
                Gl.glEnd();

                x += gridStep;
            }

            float z = -VIEWPORT_SPACE_SIZE / 2;
            for (int index = 0; index <= gridSize; index++)
            {
                Gl.glBegin(Gl.GL_LINES);
                Gl.glVertex3f(-VIEWPORT_SPACE_SIZE / 2, 0, z);
                Gl.glVertex3f(VIEWPORT_SPACE_SIZE / 2, 0, z);
                Gl.glEnd();

                z += gridStep;
            }

            Gl.glColor3fv(RSColors.ViewPort_GridCenter);
            Gl.glLineWidth(3f);

            Gl.glBegin(Gl.GL_LINES);
            Gl.glVertex3f(-VIEWPORT_SPACE_SIZE / 2, 0, 0);
            Gl.glVertex3f(VIEWPORT_SPACE_SIZE / 2, 0, 0);
            Gl.glEnd();

            Gl.glBegin(Gl.GL_LINES);
            Gl.glVertex3f(0, 0, -VIEWPORT_SPACE_SIZE / 2);
            Gl.glVertex3f(0, 0, VIEWPORT_SPACE_SIZE / 2);
            Gl.glEnd();
        }

        #endregion

        #region Render_Axis

        private void Render_Axis()
        {
            //---- Viewport size
            Gl.glViewport(0, 0, 90, 90);

            #region Look at

            //Gl.glMatrixMode(Gl.GL_PROJECTION);
            //Gl.glLoadIdentity();

            //Gl.glOrtho(-VIEWPORT_SPACE_SIZE, VIEWPORT_SPACE_SIZE, -VIEWPORT_SPACE_SIZE, VIEWPORT_SPACE_SIZE, -500, 500);

            // Look at : normalize the distance
            Vector3 eye = Camera.Eye.Clone();
            Vector3 eyeAt = Camera.EyeAt.Clone();
            Vector3 eyeNormal = Camera.EyeUp.Clone();
            float norm = (eye - eyeAt).Normalize();
            eye = eyeAt + (eye - eyeAt) / (norm * VIEWPORT_SPACE_SIZE);

            Glu.gluLookAt(
                eye.x, eye.y, eye.z,
                eyeAt.x, eyeAt.y, eyeAt.z,
                eyeNormal.x, eyeNormal.y, eyeNormal.z);

            #endregion

            //---- Reset transformations
            Gl.glLoadIdentity();

            //---- Draw
            Gl.glLineWidth(1f);

            Gl.glBegin(Gl.GL_LINES);
            Gl.glColor3f(0f, 1f, 0f);			    // Green for x axis
            Gl.glVertex3f(0f, 0f, 0f);
            Gl.glVertex3f(VIEWPORT_SPACE_SIZE / 2, 0f, 0f);
            Gl.glColor3f(1f, 0f, 0f);				// Red for y axis
            Gl.glVertex3f(0f, 0f, 0f);
            Gl.glVertex3f(0f, VIEWPORT_SPACE_SIZE / 2, 0f);
            Gl.glColor3f(0f, 0f, 1f);				// Blue for z axis
            Gl.glVertex3f(0f, 0f, 0f);
            Gl.glVertex3f(0f, 0f, VIEWPORT_SPACE_SIZE / 2);
            Gl.glEnd();

            //---- Texts
            Gl.glColor3f(0f, 1f, 0f);
            Render_Text((float)(0.7f * VIEWPORT_SPACE_SIZE), 0f, 0f, "X");

            Gl.glColor3f(1.0f, 0.0f, 0.0f);
            Render_Text(0f, (float)(0.7f * VIEWPORT_SPACE_SIZE), 0f, "Y");

            Gl.glColor3f(0.0f, 0.0f, 1.0f);
            Render_Text(0f, 0f, (float)(0.7f * VIEWPORT_SPACE_SIZE), "Z");

            //---- Reset
            Gl.glPopMatrix();
        }

        #endregion

        #region Render_Text

        private void Render_Text(float x, float y, float z, string text)
        {
            Gl.glRasterPos3f(x, y, z);

            for (int i = 0; i < text.Length; i++)
                Glut.glutBitmapCharacter(Glut.GLUT_BITMAP_TIMES_ROMAN_10, text[i]);
        }

        //private void Render_Text2(string text)
        //{
        //    int Errors;

        //    FTFont samplefont;

        //    // create font
        //    samplefont = new FTFont(@"D:\WINDOWS\Fonts\TAHOMA.TTF", out Errors);

        //    // Initialise font as a per character bitmapped font, 48x48 pix per character, 196DPI
        //    samplefont.ftRenderToTexture(48, 196);

        //    // SET the sample font to align CENTERED
        //    samplefont.FT_ALIGN = FTFontAlign.FT_ALIGN_CENTERED;

        //    // Rendering Loop
        //    //Gl.glScalef(0.004f, 0.004f, 0.004f);

        //    samplefont.ftBeginFont(false);
        //    samplefont.ftWrite(text);
        //    samplefont.ftEndFont();
        //}

        #endregion

        #region Render Guide cube

        private void Render_GuideCube()
        {
            //---- Viewport size
            Gl.glViewport((int)(oglImage.ActualWidth - GUIDECUBE_VIEWPORTSIZE), (int)(oglImage.ActualHeight - GUIDECUBE_VIEWPORTSIZE), GUIDECUBE_VIEWPORTSIZE, GUIDECUBE_VIEWPORTSIZE);

            #region Look at

            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();

            Gl.glOrtho(-GUIDECUBE_VIEWPORTSIZE, GUIDECUBE_VIEWPORTSIZE, -GUIDECUBE_VIEWPORTSIZE, GUIDECUBE_VIEWPORTSIZE, -500, 500);

            // Look at : normalize the distance
            Vector3 eye = Camera.Eye.Clone();
            Vector3 eyeAt = Camera.EyeAt.Clone();
            Vector3 eyeNormal = Camera.EyeUp.Clone();
            float norm = (eye - eyeAt).Normalize();
            eye = eyeAt + (eye - eyeAt) / (norm * GUIDECUBE_VIEWPORTSIZE);

            // Center to (0,0,0)
            eye = eye - eyeAt;
            eyeAt = new Vector3();

            Glu.gluLookAt(
                eye.x, eye.y, eye.z,
                eyeAt.x, eyeAt.y, eyeAt.z,
                eyeNormal.x, eyeNormal.y, eyeNormal.z);

            #endregion

            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();

            #region Lighting

            Gl.glEnable(Gl.GL_LIGHTING);
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_AMBIENT, _guideCubeLightAmbient);
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_DIFFUSE, _guideCubeLightDiffuseSpecular);
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_SPECULAR, _guideCubeLightDiffuseSpecular);
            Vector3 lightPos = _arcBall.GlobalRotation * LookAt(GUIDECUBE_VIEWPORTSIZE).Eye;
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_POSITION, new float[] { (float)lightPos.x, (float)lightPos.y, (float)lightPos.z, 1.0f });
            Gl.glEnable(Gl.GL_LIGHT0);

            #endregion

            #region Draw the cube

            Gl.glColor3fv(_guideCubeColor);
            Gl.glEnable(Gl.GL_COLOR_MATERIAL);

            //---- Textures
            InitializeTextures();

            Gl.glEnable(Gl.GL_TEXTURE_2D);
            Gl.glTexEnvi(Gl.GL_TEXTURE_ENV, Gl.GL_TEXTURE_ENV_MODE, Gl.GL_MODULATE);
            //Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR_MIPMAP_NEAREST);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_CLAMP);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_CLAMP);

            // Front
            Gl.glBindTexture(Gl.GL_TEXTURE_2D, _frontTextureId);
            Gl.glBegin(Gl.GL_QUADS);
            if (_guideCubeSelection == GuideCubeNames.Front)
                Gl.glColor3fv(_guideCubeHighlightColor);
            Gl.glNormal3f(0f, 0f, 1f);
            Gl.glTexCoord2f(0, 0); Gl.glVertex3f(-_cubeSize, -_cubeSize, _cubeSize);
            Gl.glTexCoord2f(1, 0); Gl.glVertex3f(_cubeSize, -_cubeSize, _cubeSize);
            Gl.glTexCoord2f(1, 1); Gl.glVertex3f(_cubeSize, _cubeSize, _cubeSize);
            Gl.glTexCoord2f(0, 1); Gl.glVertex3f(-_cubeSize, _cubeSize, _cubeSize);
            Gl.glColor3fv(_guideCubeColor);
            Gl.glEnd();

            // Back
            Gl.glBindTexture(Gl.GL_TEXTURE_2D, _backTextureId);
            Gl.glBegin(Gl.GL_QUADS);
            if (_guideCubeSelection == GuideCubeNames.Back)
                Gl.glColor3fv(_guideCubeHighlightColor);
            Gl.glNormal3f(0f, 0f, -1f);
            Gl.glTexCoord2f(0, 0); Gl.glVertex3f(_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glTexCoord2f(1, 0); Gl.glVertex3f(-_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glTexCoord2f(1, 1); Gl.glVertex3f(-_cubeSize, _cubeSize, -_cubeSize);
            Gl.glTexCoord2f(0, 1); Gl.glVertex3f(_cubeSize, _cubeSize, -_cubeSize);
            Gl.glColor3fv(_guideCubeColor);
            Gl.glEnd();

            // Left
            Gl.glBindTexture(Gl.GL_TEXTURE_2D, _leftTextureId);
            Gl.glBegin(Gl.GL_QUADS);
            if (_guideCubeSelection == GuideCubeNames.Left)
                Gl.glColor3fv(_guideCubeHighlightColor);
            Gl.glNormal3f(-1f, 0f, 0f);
            Gl.glTexCoord2f(0, 0); Gl.glVertex3f(-_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glTexCoord2f(1, 0); Gl.glVertex3f(-_cubeSize, -_cubeSize, _cubeSize);
            Gl.glTexCoord2f(1, 1); Gl.glVertex3f(-_cubeSize, _cubeSize, _cubeSize);
            Gl.glTexCoord2f(0, 1); Gl.glVertex3f(-_cubeSize, _cubeSize, -_cubeSize);
            Gl.glColor3fv(_guideCubeColor);
            Gl.glEnd();

            // Right
            Gl.glBindTexture(Gl.GL_TEXTURE_2D, _rightTextureId);
            Gl.glBegin(Gl.GL_QUADS);
            if (_guideCubeSelection == GuideCubeNames.Right)
                Gl.glColor3fv(_guideCubeHighlightColor);
            Gl.glNormal3f(1f, 0f, 0f);
            Gl.glTexCoord2f(0, 0); Gl.glVertex3f(_cubeSize, -_cubeSize, _cubeSize);
            Gl.glTexCoord2f(1, 0); Gl.glVertex3f(_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glTexCoord2f(1, 1); Gl.glVertex3f(_cubeSize, _cubeSize, -_cubeSize);
            Gl.glTexCoord2f(0, 1); Gl.glVertex3f(_cubeSize, _cubeSize, _cubeSize);
            Gl.glColor3fv(_guideCubeColor);
            Gl.glEnd();

            // Top
            Gl.glBindTexture(Gl.GL_TEXTURE_2D, _topTextureId);
            Gl.glBegin(Gl.GL_QUADS);
            if (_guideCubeSelection == GuideCubeNames.Top)
                Gl.glColor3fv(_guideCubeHighlightColor);
            Gl.glNormal3f(0f, 1f, 0f);
            Gl.glTexCoord2f(0, 0); Gl.glVertex3f(-_cubeSize, _cubeSize, _cubeSize);
            Gl.glTexCoord2f(1, 0); Gl.glVertex3f(_cubeSize, _cubeSize, _cubeSize);
            Gl.glTexCoord2f(1, 1); Gl.glVertex3f(_cubeSize, _cubeSize, -_cubeSize);
            Gl.glTexCoord2f(0, 1); Gl.glVertex3f(-_cubeSize, _cubeSize, -_cubeSize);
            Gl.glColor3fv(_guideCubeColor);
            Gl.glEnd();

            // Bottom
            Gl.glBindTexture(Gl.GL_TEXTURE_2D, _bottomTextureId);
            Gl.glBegin(Gl.GL_QUADS);
            if (_guideCubeSelection == GuideCubeNames.Bottom)
                Gl.glColor3fv(_guideCubeHighlightColor);
            Gl.glNormal3f(0f, -1f, 0f);
            Gl.glTexCoord2f(0, 0); Gl.glVertex3f(-_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glTexCoord2f(1, 0); Gl.glVertex3f(_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glTexCoord2f(1, 1); Gl.glVertex3f(_cubeSize, -_cubeSize, _cubeSize);
            Gl.glTexCoord2f(0, 1); Gl.glVertex3f(-_cubeSize, -_cubeSize, _cubeSize);
            Gl.glColor3fv(_guideCubeColor);
            Gl.glEnd();

            Gl.glDisable(Gl.GL_TEXTURE_2D);

            #endregion

            #region Draw the lines

            Gl.glLineWidth(1);
            Gl.glColor3f(1f, 1f, 1f);

            _cubeSize += 0.5f;

            Gl.glBegin(Gl.GL_LINE_LOOP);
            Gl.glVertex3f(-_cubeSize, _cubeSize, -_cubeSize);
            Gl.glVertex3f(-_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glVertex3f(_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glVertex3f(_cubeSize, _cubeSize, -_cubeSize);
            Gl.glEnd();

            Gl.glBegin(Gl.GL_LINE_LOOP);
            Gl.glVertex3f(-_cubeSize, _cubeSize, _cubeSize);
            Gl.glVertex3f(-_cubeSize, -_cubeSize, _cubeSize);
            Gl.glVertex3f(_cubeSize, -_cubeSize, _cubeSize);
            Gl.glVertex3f(_cubeSize, _cubeSize, _cubeSize);
            Gl.glEnd();

            Gl.glBegin(Gl.GL_LINE_LOOP);
            Gl.glVertex3f(-_cubeSize, _cubeSize, -_cubeSize);
            Gl.glVertex3f(-_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glVertex3f(-_cubeSize, -_cubeSize, _cubeSize);
            Gl.glVertex3f(-_cubeSize, _cubeSize, _cubeSize);
            Gl.glEnd();

            Gl.glBegin(Gl.GL_LINE_LOOP);
            Gl.glVertex3f(_cubeSize, _cubeSize, -_cubeSize);
            Gl.glVertex3f(_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glVertex3f(_cubeSize, -_cubeSize, _cubeSize);
            Gl.glVertex3f(_cubeSize, _cubeSize, _cubeSize);
            Gl.glEnd();

            _cubeSize -= 0.5f;

            #endregion

            #region Reset light

            float[] zero = new float[] { 0f, 0f, 0f, 1f };
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_AMBIENT, zero);
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_DIFFUSE, zero);
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_SPECULAR, zero);
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_POSITION, new float[] { 0f, 0f, 1f, 0f });

            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_SPOT_DIRECTION, new float[] { 0f, 0f, -1f });
            Gl.glLightf(Gl.GL_LIGHT0, Gl.GL_SPOT_CUTOFF, 180);
            Gl.glLightf(Gl.GL_LIGHT0, Gl.GL_SPOT_EXPONENT, 0);

            Gl.glLightf(Gl.GL_LIGHT0, Gl.GL_CONSTANT_ATTENUATION, 1);
            Gl.glLightf(Gl.GL_LIGHT0, Gl.GL_LINEAR_ATTENUATION, 0);
            Gl.glLightf(Gl.GL_LIGHT0, Gl.GL_QUADRATIC_ATTENUATION, 0);
            Gl.glDisable(Gl.GL_LIGHT0);

            Gl.glDisable(Gl.GL_LIGHTING);

            #endregion

        }

        private void InitializeTextures()
        {
            if (_frontTextureId > -1)
                return;

            _frontTextureId = InitializeTexture(@"/images/GuideCube/FRONT.png");
            _backTextureId = InitializeTexture(@"/images/GuideCube/BACK.png");
            _leftTextureId = InitializeTexture(@"/images/GuideCube/LEFT.png");
            _rightTextureId = InitializeTexture(@"/images/GuideCube/RIGHT.png");
            _topTextureId = InitializeTexture(@"/images/GuideCube/TOP.png");
            _bottomTextureId = InitializeTexture(@"/images/GuideCube/BOTTOM.png");
        }

        private int InitializeTexture(string path)
        {
            int textureId = -1;
            string root = System.Windows.Forms.Application.StartupPath;

            Stream stream = Application.GetResourceStream(new Uri(@"pack://application:,,," + path)).Stream;
            System.Drawing.Bitmap image = new System.Drawing.Bitmap(stream);
            image.RotateFlip(System.Drawing.RotateFlipType.RotateNoneFlipY);

            System.Drawing.Imaging.BitmapData data = image.LockBits(new System.Drawing.Rectangle(0, 0, 200, 200), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);

            Gl.glEnable(Gl.GL_TEXTURE_2D);

            Gl.glGenTextures(1, out textureId);
            Gl.glBindTexture(Gl.GL_TEXTURE_2D, textureId);

            Gl.glPixelStorei(Gl.GL_UNPACK_ALIGNMENT, 1);

            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_REPEAT);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_REPEAT);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR);

            Gl.glTexEnvf(Gl.GL_TEXTURE_ENV, Gl.GL_TEXTURE_ENV_MODE, Gl.GL_MODULATE);

            Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, Gl.GL_RGB8, 200, 200, 0, Gl.GL_BGR_EXT, Gl.GL_UNSIGNED_BYTE, data.Scan0);

            image.UnlockBits(data);
            image.Dispose();

            Gl.glBindTexture(Gl.GL_TEXTURE_2D, 0);

            return textureId;
        }

        #endregion

        #region PickingGuideCube

        private void PickingGuideCube(Point mouse)
        {
            Gl.glDrawBuffer(Gl.GL_BACK);
            Gl.glReadBuffer(Gl.GL_BACK);

            //---- Viewport size
            Gl.glViewport((int)(oglImage.ActualWidth - GUIDECUBE_VIEWPORTSIZE), (int)(oglImage.ActualHeight - GUIDECUBE_VIEWPORTSIZE), GUIDECUBE_VIEWPORTSIZE, GUIDECUBE_VIEWPORTSIZE);

            #region Look at

            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();

            Gl.glOrtho(-GUIDECUBE_VIEWPORTSIZE, GUIDECUBE_VIEWPORTSIZE, -GUIDECUBE_VIEWPORTSIZE, GUIDECUBE_VIEWPORTSIZE, -500, 500);

            // Look at : normalize the distance
            Vector3 eye = Camera.Eye.Clone();
            Vector3 eyeAt = Camera.EyeAt.Clone();
            Vector3 eyeNormal = Camera.EyeUp.Clone();
            float norm = (eye - eyeAt).Normalize();
            eye = eyeAt + (eye - eyeAt) / (norm * GUIDECUBE_VIEWPORTSIZE);

            // Center to (0,0,0)
            eye = eye - eyeAt;
            eyeAt = new Vector3();

            Glu.gluLookAt(
                eye.x, eye.y, eye.z,
                eyeAt.x, eyeAt.y, eyeAt.z,
                eyeNormal.x, eyeNormal.y, eyeNormal.z);

            #endregion

            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();

            #region Clear the buffer

            Gl.glClearColor(0f, 0f, 0f, 0f);
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

            #endregion

            Gl.glDisable(Gl.GL_LIGHTING);
            Gl.glDisable(Gl.GL_DITHER);
            Gl.glDisable(Gl.GL_BLEND);
            Gl.glDisable(Gl.GL_TEXTURE_2D);
            Gl.glEnable(Gl.GL_COLOR_MATERIAL);

            #region Draw the cube

            // Front
            Gl.glColor3f(1f / 255, 0, 0);
            Gl.glBegin(Gl.GL_QUADS);
            Gl.glNormal3f(0f, 0f, 1f);
            Gl.glVertex3f(-_cubeSize, -_cubeSize, _cubeSize);
            Gl.glVertex3f(_cubeSize, -_cubeSize, _cubeSize);
            Gl.glVertex3f(_cubeSize, _cubeSize, _cubeSize);
            Gl.glVertex3f(-_cubeSize, _cubeSize, _cubeSize);
            Gl.glEnd();

            // Back
            Gl.glColor3f(2f / 255, 0, 0);
            Gl.glBegin(Gl.GL_QUADS);
            Gl.glNormal3f(0f, 0f, -1f);
            Gl.glVertex3f(_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glVertex3f(-_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glVertex3f(-_cubeSize, _cubeSize, -_cubeSize);
            Gl.glVertex3f(_cubeSize, _cubeSize, -_cubeSize);
            Gl.glEnd();

            // Top
            Gl.glColor3f(3f / 255, 0, 0);
            Gl.glBegin(Gl.GL_QUADS);
            Gl.glNormal3f(0f, 1f, 0f);
            Gl.glVertex3f(-_cubeSize, _cubeSize, _cubeSize);
            Gl.glVertex3f(_cubeSize, _cubeSize, _cubeSize);
            Gl.glVertex3f(_cubeSize, _cubeSize, -_cubeSize);
            Gl.glVertex3f(-_cubeSize, _cubeSize, -_cubeSize);
            Gl.glEnd();

            // Bottom
            Gl.glColor3f(4f / 255, 0, 0);
            Gl.glBegin(Gl.GL_QUADS);
            Gl.glNormal3f(0f, -1f, 0f);
            Gl.glVertex3f(-_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glVertex3f(_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glVertex3f(_cubeSize, -_cubeSize, _cubeSize);
            Gl.glVertex3f(-_cubeSize, -_cubeSize, _cubeSize);
            Gl.glEnd();

            // Left
            Gl.glColor3f(5f / 255, 0, 0);
            Gl.glBegin(Gl.GL_QUADS);
            Gl.glNormal3f(-1f, 0f, 0f);
            Gl.glVertex3f(-_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glVertex3f(-_cubeSize, -_cubeSize, _cubeSize);
            Gl.glVertex3f(-_cubeSize, _cubeSize, _cubeSize);
            Gl.glVertex3f(-_cubeSize, _cubeSize, -_cubeSize);
            Gl.glEnd();

            // Right
            Gl.glColor3f(6f / 255, 0, 0);
            Gl.glBegin(Gl.GL_QUADS);
            Gl.glNormal3f(1f, 0f, 0f);
            Gl.glVertex3f(_cubeSize, -_cubeSize, _cubeSize);
            Gl.glVertex3f(_cubeSize, -_cubeSize, -_cubeSize);
            Gl.glVertex3f(_cubeSize, _cubeSize, -_cubeSize);
            Gl.glVertex3f(_cubeSize, _cubeSize, _cubeSize);
            Gl.glEnd();

            #endregion

            GuideCubeNames selection = PickingGuideCube_GetSelectedFace(mouse);

            if (selection != _guideCubeSelection)
            {
                _guideCubeSelection = selection;
                oglImage.Refresh();
            }
        }

        private GuideCubeNames PickingGuideCube_GetSelectedFace(Point mouse)
        {
            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glPopMatrix();
            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glFlush();

            mouse.Y = (int)(oglImage.ActualHeight - 1 - mouse.Y);

            Gl.glFlush();
            Gl.glFinish();

            // Read pixel
            System.Drawing.Imaging.BitmapData bd = _selectionBitmap.LockBits(new System.Drawing.Rectangle(0, 0, 1, 1), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            Gl.glReadPixels((int)mouse.X, (int)mouse.Y, 1, 1, Gl.GL_BGRA, Gl.GL_UNSIGNED_BYTE, bd.Scan0);
            _selectionBitmap.UnlockBits(bd);
            System.Drawing.Color c = _selectionBitmap.GetPixel(0, 0);

            //IntPtr data_pointer = Marshal.AllocHGlobal(4); // Allocate 4 bytes
            //Gl.glReadPixels((int)mouse.X, (int)mouse.Y, 1, 1, Gl.GL_BGRA, Gl.GL_UNSIGNED_BYTE, data_pointer);
            //System.Drawing.Color c = System.Drawing.Color.FromArgb(Marshal.ReadInt32(data_pointer));
            //Marshal.FreeHGlobal(data_pointer);

            #region Get the Z coordinate for the pixel location
            /*
            glReadPixels(lMousePos.x, (lViewPort[3] - lMousePos.y), 1, 1, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, &lDepth);
            int zdepth;
            float lDistTermInv;
            glGetIntegerv(GL_DEPTH_BITS, &zdepth);
            switch (zdepth)
            {
                case 16:
                    lDistTermInv = (float)1.5259018967e-5; // 65535
                    break;
                default:
                    lDistTermInv = (float)2.32830643708e-10; // 4294967295
                    break;
            }

            lDistance = (float)lDepth * lDistTermInv;
            glGetDoublev( GL_MODELVIEW_MATRIX, lModelMatrix );
            glGetDoublev( GL_PROJECTION_MATRIX, lProjMatrix );
            gluUnProject( lMousePos.x, ( lViewPort[ 3 ] - lMousePos.y ), 
            lDistance, lModelMatrix, lProjMatrix, lViewPort,
            &lPos[ 0 ], &lPos[ 1 ], &lPos[ 2 ] );
            ret.x = lPos[ 0 ];
            ret.y = lPos[ 1 ];
            ret.z = lPos[ 2 ];
            */
            #endregion

            switch (c.R)
            {
                case 1: return GuideCubeNames.Front;
                case 2: return GuideCubeNames.Back;
                case 3: return GuideCubeNames.Top;
                case 4: return GuideCubeNames.Bottom;
                case 5: return GuideCubeNames.Left;
                case 6: return GuideCubeNames.Right;
            }

            return GuideCubeNames.None;
        }

        #endregion

        #region Event : Shading modes

        private void UpdateShadingModeControls()
        {
            miWireframe.IsChecked = (_parameters.RenderingStyle == GLRenderParameters.RenderStyle.Wireframe);
            rbWireframe.IsChecked = (_parameters.RenderingStyle == GLRenderParameters.RenderStyle.Wireframe);

            miWireframeOnShaded.IsChecked = (_parameters.RenderingStyle == GLRenderParameters.RenderStyle.WireframeOnShaded);
            rbWireframeOnShaded.IsChecked = (_parameters.RenderingStyle == GLRenderParameters.RenderStyle.WireframeOnShaded);

            miShaded.IsChecked = (_parameters.RenderingStyle == GLRenderParameters.RenderStyle.Shaded);
            rbShaded.IsChecked = (_parameters.RenderingStyle == GLRenderParameters.RenderStyle.Shaded);
        }

        private void Wireframe_Click(object sender, RoutedEventArgs e)
        {
            _parameters.RenderingStyle = GLRenderParameters.RenderStyle.Wireframe;
            UpdateShadingModeControls();
            oglImage.Refresh();
        }

        private void Shaded_Click(object sender, RoutedEventArgs e)
        {
            _parameters.RenderingStyle = GLRenderParameters.RenderStyle.Shaded;
            UpdateShadingModeControls();
            oglImage.Refresh();
        }

        private void WireframeOnShaded_Click(object sender, RoutedEventArgs e)
        {
            _parameters.RenderingStyle = GLRenderParameters.RenderStyle.WireframeOnShaded;
            UpdateShadingModeControls();
            oglImage.Refresh();
        }

        private void ShowLight_Click(object sender, RoutedEventArgs e)
        {
            _parameters.ShowLightning = !_parameters.ShowLightning;
            UpdateShadingModeControls();
            oglImage.Refresh();
        }

        private void ShowTexture_Click(object sender, RoutedEventArgs e)
        {
            _parameters.ShowTextures = !_parameters.ShowTextures;
            UpdateShadingModeControls();
            oglImage.Refresh();
        }

        private void ShowNormal_Click(object sender, RoutedEventArgs e)
        {
            _parameters.ShowNormals = !_parameters.ShowNormals;
            UpdateShadingModeControls();
            oglImage.Refresh();
        }

        #endregion

        #region AutoSwitchLayout

        private void AutoSwitchLayout()
        {
            // LayoutType
            if (_guideCubeSelection == GuideCubeNames.None)
                return;

            ControlType targetLayout = ControlType.ViewPort_Front;
            switch (_guideCubeSelection)
            {
                case GuideCubeNames.Front:
                    targetLayout = ControlType.ViewPort_Front;
                    break;
                case GuideCubeNames.Back:
                    targetLayout = ControlType.ViewPort_Back;
                    break;
                case GuideCubeNames.Left:
                    targetLayout = ControlType.ViewPort_Left;
                    break;
                case GuideCubeNames.Right:
                    targetLayout = ControlType.ViewPort_Right;
                    break;
                case GuideCubeNames.Top:
                    targetLayout = ControlType.ViewPort_Top;
                    break;
                case GuideCubeNames.Bottom:
                    targetLayout = ControlType.ViewPort_Bottom;
                    break;
            }

            // Get the new target layout
            float distance = (Camera.Eye - Camera.EyeAt).Length;
            Camera targetLookAt = LookAt(targetLayout, distance);

            targetLookAt.Eye += Camera.EyeAt;
            targetLookAt.EyeAt += Camera.EyeAt;

            // Animate
            AnimateLookAt(targetLookAt);
        }

        int animationIndex = 0;
        Camera animationDelta = new Camera(null);

        private void AnimateLookAt(Camera targetLookAt)
        {
            animationDelta.Eye = (targetLookAt.Eye - Camera.Eye) / 10;
            animationDelta.EyeAt = (targetLookAt.EyeAt - Camera.EyeAt) / 10;
            animationDelta.EyeUp = (targetLookAt.EyeUp - Camera.EyeUp) / 10;

            animationIndex = 0;

            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
        }

        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            Camera.Eye += animationDelta.Eye;
            Camera.EyeAt += animationDelta.EyeAt;
            Camera.EyeUp += animationDelta.EyeUp;
            oglImage.Refresh();

            animationIndex++;

            if (animationIndex == 10)
            {
                CompositionTarget.Rendering -= new EventHandler(CompositionTarget_Rendering);

                UpdateArcBall();
            }
        }

        private void UpdateArcBall()
        {
            //---- Recalculate the axis
            Vector3 zAxis = Camera.Eye - Camera.EyeAt; // zAxis is the inverse of the direction
            zAxis.Normalize();
            Vector3 xAxis = Camera.EyeUp.Cross(zAxis);
            xAxis.Normalize();
            Vector3 yAxis = zAxis.Cross(xAxis);
            yAxis.Normalize();

            //---- Create a rotation matrix
            Matrix4 rotation = Matrix4.Identity.Clone();

            rotation.M00 = xAxis.x;
            rotation.M10 = xAxis.y;
            rotation.M20 = xAxis.z;

            rotation.M01 = yAxis.x;
            rotation.M11 = yAxis.y;
            rotation.M21 = yAxis.z;

            rotation.M02 = zAxis.x;
            rotation.M12 = zAxis.y;
            rotation.M22 = zAxis.z;

            _arcBall.GlobalRotation = rotation;
        }

        #endregion

        #region DetachACopy_Click

        private void DetachACopy_Click(object sender, RoutedEventArgs e)
        {
            Arranger.DetachACopy(_layoutType);
        }

        #endregion

        #region Helpers

        private void ConvertMouseToScene(ref double x, ref double y)
        {
            double aspectRatioY = 1;
            if (oglImage.ActualHeight > 0)
                aspectRatioY = 1 / oglImage.ActualHeight;

            x = x * aspectRatioY;
            y = y * aspectRatioY;
        }

        void gluPerspective(float fovy, float aspect, float zmin, float zmax)
        {
            float xmin, xmax, ymin, ymax;
            ymax = zmin * (float)Math.Tan(fovy * Math.PI / 360.0);
            ymin = -ymax;
            xmin = ymin * aspect;
            xmax = ymax * aspect;
            Gl.glFrustum(xmin, xmax, ymin, ymax, zmin, zmax);

            //Gl.glFrustum(-aspect * near * Math.Tan(Math.PI * fovy / 360), aspect * near * Math.Tan(Math.PI * fovy / 360), -near * Math.Tan(Math.PI * fovy / 360), near * Math.Tan(Math.PI * fovy / 360), near, far);
        }

        void gluProject(float objX, float objY, float objZ, float[] modelMatrix, float[] projectionMatrix, int[] viewport, out float winX, out float winY, out float winZ)
        {
            double[] mm = new double[16];
            double[] pm = new double[16];

            for (int index = 0; index < 16; index++)
            {
                mm[index] = modelMatrix[index];
                pm[index] = projectionMatrix[index];
            }

            double windowX;
            double windowY;
            double windowZ;

            Glu.gluProject(objX, objY, objZ, mm, pm, viewport, out windowX, out windowY, out windowZ);

            winX = (float)windowX;
            winY = (float)windowY;
            winZ = (float)windowZ;
        }

        void gluUnProject(float winX, float winY, float winZ, float[] modelMatrix, float[] projectionMatrix, int[] viewport, out float objX, out float objY, out float objZ)
        {
            double[] mm = new double[16];
            double[] pm = new double[16];

            for (int index = 0; index < 16; index++)
            {
                mm[index] = modelMatrix[index];
                pm[index] = projectionMatrix[index];
            }

            double objectX;
            double objectY;
            double objectZ;

            Glu.gluUnProject((double)winX, (double)winY, (double)winZ, mm, pm, viewport, out objectX, out objectY, out objectZ);


            objX = (float)objectX;
            objY = (float)objectY;
            objZ = (float)objectZ;
        }

        // GL_DEPTH_COMPONENT
        // Depth values are read from the depth buffer. Each component is converted to floating point such hat the minimum depth value maps to 0 and the maximum value maps to 1. Each component is then multiplied by GL_DEPTH_SCALE, added to GL_DEPTH_BIAS, and finally clamped to the range [0,1].

        #endregion

        #region Docs...

        // Calculate ZDepth
        /*
            float distance = float.MinValue;
            unsafe
            {
                Gl.glReadPixels((int)oglImage.ActualWidth / 2, (int)oglImage.ActualHeight / 2, 1, 1, Gl.GL_DEPTH_COMPONENT, Gl.GL_FLOAT, new IntPtr(&distance)); // get Z position
            }

            float lDistTermInv;
            int zdepth;
            unsafe
            {
                Gl.glGetIntegerv(Gl.GL_DEPTH_BITS, new IntPtr(&zdepth));
            }
            switch (zdepth)
            {
                case 16:
                    lDistTermInv = (float)1.5259018967e-5; // 65535
                    break;
                default:
                    lDistTermInv = (float)2.32830643708e-10; // 4294967295
                    break;
            }

            distance = (float)distance * lDistTermInv;
         */

        // How can I automatically calculate a view that displays my entire model? (I know the bounding sphere and up vector.)
        // http://www.opengl.org/resources/faq/technical/viewing.htm

        #endregion

    }

    #region enum : GuideCubeNames

    public enum GuideCubeNames
    {
        None = 0,
        Front = 1,
        Back = 2,
        Top = 3,
        Bottom = 4,
        Left = 5,
        Right = 6
    }

    #endregion

}

